GDK: Add cursor theme support to W32 backend
authorРуслан Ижбулатов <lrn1986@gmail.com>
Wed, 13 May 2015 07:45:40 +0000 (07:45 +0000)
committerРуслан Ижбулатов <lrn1986@gmail.com>
Wed, 20 May 2015 08:42:24 +0000 (08:42 +0000)
Load themed cursors from the same places they are loaded on freedesktop systems,
but use W32 API functions to do so (works for .cur/.ani cursors instead of X
cursors).

Refactor the code for cursor handling. Prefer loading cursors by name.

Do not load actual cursors when loading the theme. Find the files and remember
the arguments/calls for loading them instead. Keeping HCURSOR instance in the
hashmap would result in multiple GdkCursors using the same HCURSOR. Given that
we use DestroyCursor() to off them, this would cause problems (at the very
least - DestroyCursor() would fail).

Store GdkCursor instances in a cache. Update cached cursors when theme changes.

Recognize "system" theme as a special (and default) case. When it is set,
prefer system cursors and fall back to Adwaita cursors and (as a last resort)
built-in X cursors. Otherwise prefer theme cursors and fall back to system and
X cursors.

Force GTK to use "left_ptr" cursor when no cursor is set. Using NULL makes
it use the system default "arrow", which is not the intended behaviour when
a non-system theme is selected.

Ignore cursor size setting and query the OS for the required cursor size, as
Windows (almost) does not allow setting cursors of arbitrary size.

https://bugzilla.gnome.org/show_bug.cgi?id=749287

docs/reference/gtk/windows.sgml
gdk/win32/gdkcursor-win32.c
gdk/win32/gdkdisplay-win32.c
gdk/win32/gdkdisplay-win32.h
gdk/win32/gdkprivate-win32.h
gdk/win32/gdkwin32display.h
gdk/win32/gdkwindow-win32.c
gtk/gtksettings.c

index f779b86d8a7b14819725d418ea8c42bff4bd3b93..e00bdbbe74ea122b5eeab3289ca376ac43c031cd 100644 (file)
@@ -107,6 +107,33 @@ in 256 color mode.
 
 </refsect2>
 
+<refsect2 id="win32-cursors">
+<title>Windows-specific handling of cursors</title>
+
+<para>
+By default the "system" cursor theme is used. This makes GTK prefer cursors
+that Windows currently uses, falling back to Adwaita cursors and (as the last
+resort) built-in X cursors.
+</para>
+<para>
+When any other cursor theme is used, GTK will prefer cursors from that theme,
+falling back to Windows cursors and built-in X cursors.
+</para>
+<para>
+Theme can be changed by setting <literal>gtk-cursor-theme-name</literal> GTK+ setting. Users can override GTK+ settings in the <filename>settings.ini</filename> file or at runtime in the GTK+ Inspector.
+</para>
+<para>
+Themes are loaded from normal Windows variants of the XDG locations:
+<filename>%HOME%/icons/THEME/cursors</filename>, 
+<filename>%APPDATA%/icons/THEME/cursors</filename>, 
+<filename>RUNTIME_PREFIX/share/icons/THEME/cursors</filename>.
+</para>
+<para>
+The <literal>gtk-cursor-theme-size</literal> setting is ignored, GTK will use the cursor size that Windows tells it to use.
+</para>
+
+</refsect2>
+
 <para>
 More information about GTK+ on Windows, including detailed build
 instructions, binary downloads, etc, can be found
index c88d899000f16e3a3d7ca3563a6d22ee2255d246..290389b2101cdbdd7c441b5fd9167bf065679b30 100644 (file)
 #include "gdkcursor.h"
 #include "gdkwin32.h"
 
+#include "gdkdisplay-win32.h"
+
 #ifdef __MINGW32__
 #include <w32api.h>
 #endif
 
 #include "xcursors.h"
 
+typedef struct _DefaultCursor {
+  char *name;
+  char *id;
+} DefaultCursor;
+
+static DefaultCursor default_cursors[] = {
+  { "appstarting", IDC_APPSTARTING },
+  { "arrow", IDC_ARROW },
+  { "cross", IDC_CROSS },
+  { "hand",  IDC_HAND },
+  { "help",  IDC_HELP },
+  { "ibeam", IDC_IBEAM },
+  /* an X cursor name, for compatibility with GTK: */
+  { "left_ptr_watch", IDC_APPSTARTING },
+  { "sizeall", IDC_SIZEALL },
+  { "sizenesw", IDC_SIZENESW },
+  { "sizens", IDC_SIZENS },
+  { "sizenwse", IDC_SIZENWSE },
+  { "sizewe", IDC_SIZEWE },
+  { "uparrow", IDC_UPARROW },
+  { "wait", IDC_WAIT },
+  /* css cursor names: */
+  { "default", IDC_ARROW },
+  { "pointer", IDC_HAND },
+  { "progress", IDC_APPSTARTING },
+  { "crosshair", IDC_CROSS },
+  { "text", IDC_IBEAM },
+  { "move", IDC_SIZEALL },
+  { "not-allowed", IDC_NO },
+  { "ew-resize", IDC_SIZEWE },
+  { "ns-resize", IDC_SIZENS },
+  { "nesw-resize", IDC_SIZENESW },
+  { "nwse-resize", IDC_SIZENWSE }
+};
+
 static HCURSOR
-hcursor_from_type (GdkCursorType cursor_type)
+hcursor_from_x_cursor (gint          i,
+                       GdkCursorType cursor_type)
 {
-  gint i, j, x, y, ofs;
+  gint j, x, y, ofs;
   HCURSOR rv;
   gint w, h;
   guchar *and_plane, *xor_plane;
 
-  if (cursor_type != GDK_BLANK_CURSOR)
-    {
-      for (i = 0; i < G_N_ELEMENTS (cursors); i++)
-       if (cursors[i].type == cursor_type)
-         break;
-
-      if (i >= G_N_ELEMENTS (cursors) || !cursors[i].name)
-       return NULL;
-
-      /* Use real Win32 cursor if possible */
-      if (cursors[i].builtin)
-       return LoadCursor (NULL, cursors[i].builtin);
-    }
-
   w = GetSystemMetrics (SM_CXCURSOR);
   h = GetSystemMetrics (SM_CYCURSOR);
 
@@ -79,6 +103,7 @@ hcursor_from_type (GdkCursorType cursor_type)
              if (data)
                {
                  RESET_BIT (and_plane[pofs], bit);
+
                  if (data == 1)
                    SET_BIT (xor_plane[pofs], bit);
                }
@@ -96,14 +121,307 @@ hcursor_from_type (GdkCursorType cursor_type)
       rv = CreateCursor (_gdk_app_hmodule, 0, 0,
                         w, h, and_plane, xor_plane);
     }
+
   if (rv == NULL)
     WIN32_API_FAILED ("CreateCursor");
+
   g_free (and_plane);
   g_free (xor_plane);
 
   return rv;
 }
 
+static HCURSOR
+win32_cursor_create_hcursor (Win32Cursor *cursor)
+{
+  HCURSOR result;
+
+  switch (cursor->load_type)
+    {
+      case GDK_WIN32_CURSOR_LOAD_FROM_FILE:
+        result = LoadImageW (NULL,
+                             cursor->resource_name,
+                             IMAGE_CURSOR,
+                             cursor->width,
+                             cursor->height,
+                             cursor->load_flags);
+        break;
+      case GDK_WIN32_CURSOR_LOAD_FROM_RESOURCE_NULL:
+        result = LoadImageA (NULL,
+                             (const gchar *) cursor->resource_name,
+                             IMAGE_CURSOR,
+                             cursor->width,
+                             cursor->height,
+                             cursor->load_flags);
+        break;
+      case GDK_WIN32_CURSOR_LOAD_FROM_RESOURCE_THIS:
+        result = LoadImageA (_gdk_app_hmodule,
+                             (const gchar *) cursor->resource_name,
+                             IMAGE_CURSOR,
+                             cursor->width,
+                             cursor->height,
+                             cursor->load_flags);
+        break;
+      case GDK_WIN32_CURSOR_CREATE:
+        result = hcursor_from_x_cursor (cursor->xcursor_number,
+                                        cursor->cursor_type);
+        break;
+      default:
+        result = NULL;
+    }
+
+  return result;
+}
+
+static Win32Cursor *
+win32_cursor_new (GdkWin32CursorLoadType load_type,
+                  gpointer               resource_name,
+                  gint                   width,
+                  gint                   height,
+                  guint                  load_flags,
+                  gint                   xcursor_number,
+                  GdkCursorType          cursor_type)
+{
+  Win32Cursor *result;
+
+  result = g_new (Win32Cursor, 1);
+  result->load_type = load_type;
+  result->resource_name = resource_name;
+  result->width = width;
+  result->height = height;
+  result->load_flags = load_flags;
+  result->xcursor_number = xcursor_number;
+  result->cursor_type = cursor_type;
+
+  return result;
+}
+
+
+static void
+win32_cursor_destroy (gpointer data)
+{
+  Win32Cursor *cursor = data;
+
+  /* resource_name could be a resource ID (uint16_t stored as a pointer),
+   * which shouldn't be freed.
+   */
+  if (cursor->load_type == GDK_WIN32_CURSOR_LOAD_FROM_FILE)
+    g_free (cursor->resource_name);
+
+  g_free (cursor);
+}
+
+static void
+win32_cursor_theme_load_from (Win32CursorTheme *theme,
+                              gint              size,
+                              const gchar      *dir)
+{
+  GDir *gdir;
+  const gchar *filename;
+  HCURSOR hcursor;
+
+  gdir = g_dir_open (dir, 0, NULL);
+
+  if (gdir == NULL)
+    return;
+
+  while ((filename = g_dir_read_name (gdir)) != NULL)
+    {
+      gchar *fullname;
+      gunichar2 *filenamew;
+      gchar *cursor_name;
+      gchar *dot;
+      Win32Cursor *cursor;
+
+      fullname = g_strconcat (dir, "/", filename, NULL);
+      filenamew = g_utf8_to_utf16 (fullname, -1, NULL, NULL, NULL);
+      g_free (fullname);
+
+      if (filenamew == NULL)
+        continue;
+
+      hcursor = LoadImageW (NULL, filenamew, IMAGE_CURSOR, size, size,
+                            LR_LOADFROMFILE | (size == 0 ? LR_DEFAULTSIZE : 0));
+
+      if (hcursor == NULL)
+        {
+          g_free (filenamew);
+          continue;
+        }
+
+      DestroyCursor (hcursor);
+      dot = strchr (filename, '.');
+
+      cursor_name = dot ? g_strndup (filename, dot - filename) : g_strdup (filename);
+
+      cursor = win32_cursor_new (GDK_WIN32_CURSOR_LOAD_FROM_FILE,
+                                 filenamew,
+                                 size,
+                                 size,
+                                 LR_LOADFROMFILE | (size == 0 ? LR_DEFAULTSIZE : 0),
+                                 0,
+                                 0);
+      g_hash_table_insert (theme->named_cursors, cursor_name, cursor);
+    }
+}
+
+static void
+win32_cursor_theme_load_from_dirs (Win32CursorTheme *theme,
+                                   const gchar      *name,
+                                   gint              size)
+{
+  gchar *theme_dir;
+  const gchar * const *dirs;
+  gint i;
+
+  dirs = g_get_system_data_dirs ();
+
+  /* <prefix>/share/icons */
+  for (i = 0; dirs[i]; i++)
+    {
+      theme_dir = g_strconcat (dirs[i], "/icons/", name, "/cursors", NULL);
+      win32_cursor_theme_load_from (theme, size, theme_dir);
+      g_free (theme_dir);
+    }
+
+  /* ~/.icons */
+  theme_dir = g_strconcat (g_get_home_dir (), "/icons/", name, "/cursors", NULL);
+  win32_cursor_theme_load_from (theme, size, theme_dir);
+  g_free (theme_dir);
+}
+
+static void
+win32_cursor_theme_load_system (Win32CursorTheme *theme,
+                                gint              size)
+{
+  gint i;
+  HCURSOR hcursor;
+  Win32Cursor *cursor;
+
+  for (i = 0; i < G_N_ELEMENTS (cursors); i++)
+    {
+
+      if (cursors[i].name == NULL)
+        break;
+
+      hcursor = NULL;
+
+      /* Prefer W32 cursors */
+      if (cursors[i].builtin)
+        hcursor = LoadImageA (NULL, cursors[i].builtin, IMAGE_CURSOR,
+                              size, size,
+                              LR_SHARED | (size == 0 ? LR_DEFAULTSIZE : 0));
+
+      /* Fall back to X cursors, but only if we've got no theme cursor */
+      if (hcursor == NULL && g_hash_table_lookup (theme->named_cursors, cursors[i].name) == NULL)
+        hcursor = hcursor_from_x_cursor (i, cursors[i].type);
+
+      if (hcursor == NULL)
+        continue;
+
+      DestroyCursor (hcursor);
+      cursor = win32_cursor_new (GDK_WIN32_CURSOR_LOAD_FROM_RESOURCE_NULL,
+                                 (gpointer) cursors[i].builtin,
+                                 size,
+                                 size,
+                                 LR_SHARED | (size == 0 ? LR_DEFAULTSIZE : 0),
+                                 0,
+                                 0);
+      g_hash_table_insert (theme->named_cursors,
+                           g_strdup (cursors[i].name),
+                           cursor);
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (default_cursors); i++)
+    {
+      if (default_cursors[i].name == NULL)
+        break;
+
+      hcursor = LoadImageA (NULL, default_cursors[i].id, IMAGE_CURSOR, size, size,
+                            LR_SHARED | (size == 0 ? LR_DEFAULTSIZE : 0));
+
+      if (hcursor == NULL)
+        continue;
+
+      DestroyCursor (hcursor);
+      cursor = win32_cursor_new (GDK_WIN32_CURSOR_LOAD_FROM_RESOURCE_NULL,
+                                 (gpointer) default_cursors[i].id,
+                                 size,
+                                 size,
+                                 LR_SHARED | (size == 0 ? LR_DEFAULTSIZE : 0),
+                                 0,
+                                 0);
+      g_hash_table_insert (theme->named_cursors,
+                           g_strdup (default_cursors[i].name),
+                           cursor);
+    }
+}
+
+Win32CursorTheme *
+win32_cursor_theme_load (const gchar *name,
+                         gint         size)
+{
+  Win32CursorTheme *result = g_new0 (Win32CursorTheme, 1);
+
+  result->named_cursors = g_hash_table_new_full (g_str_hash,
+                                                 g_str_equal,
+                                                 g_free,
+                                                 win32_cursor_destroy);
+
+  if (strcmp (name, "system") == 0)
+    {
+      win32_cursor_theme_load_from_dirs (result, "Adwaita", size);
+      win32_cursor_theme_load_system (result, size);
+    }
+  else
+    {
+      win32_cursor_theme_load_from_dirs (result, name, size);
+    }
+
+  if (g_hash_table_size (result->named_cursors) > 0)
+    return result;
+
+  win32_cursor_theme_destroy (result);
+  return NULL;
+}
+
+void
+win32_cursor_theme_destroy (Win32CursorTheme *theme)
+{
+  g_hash_table_destroy (theme->named_cursors);
+  g_free (theme);
+}
+
+Win32Cursor *
+win32_cursor_theme_get_cursor (Win32CursorTheme *theme,
+                               const gchar      *name)
+{
+  return g_hash_table_lookup (theme->named_cursors, name);
+}
+
+static HCURSOR
+hcursor_from_type (GdkCursorType cursor_type)
+{
+  gint i = 0;
+
+  if (cursor_type != GDK_BLANK_CURSOR)
+    {
+      for (i = 0; i < G_N_ELEMENTS (cursors); i++)
+       if (cursors[i].type == cursor_type)
+         break;
+
+      if (i >= G_N_ELEMENTS (cursors) || !cursors[i].name)
+       return NULL;
+
+      /* Use real Win32 cursor if possible */
+      if (cursors[i].builtin)
+        return LoadImageA (NULL, cursors[i].builtin, IMAGE_CURSOR, 0, 0,
+                           LR_SHARED | LR_DEFAULTSIZE);
+    }
+
+  return hcursor_from_x_cursor (i, cursor_type);
+}
+
 struct _GdkWin32CursorClass
 {
   GdkCursorClass cursor_class;
@@ -120,14 +438,91 @@ _gdk_win32_cursor_finalize (GObject *object)
     SetCursor (NULL);
 
   if (!DestroyCursor (private->hcursor))
-    WIN32_API_FAILED ("DestroyCursor");
+    g_warning (G_STRLOC ": DestroyCursor (%p) failed: %lu", private->hcursor, GetLastError ());
+
+  g_free (private->name);
 
   G_OBJECT_CLASS (gdk_win32_cursor_parent_class)->finalize (object);
 }
 
+static HCURSOR
+hcursor_idc_from_name (const gchar *name)
+{
+  int i;
+
+  for (i = 0; i < G_N_ELEMENTS (default_cursors); i++)
+    {
+      if (strcmp (default_cursors[i].name, name) != 0)
+        continue;
+
+      return LoadImageA (NULL, default_cursors[i].id, IMAGE_CURSOR, 0, 0,
+                         LR_SHARED | LR_DEFAULTSIZE);
+    }
+
+  return NULL;
+}
+
+static HCURSOR
+hcursor_x_from_name (const gchar *name)
+{
+  gint i;
+
+  for (i = 0; i < G_N_ELEMENTS (cursors); i++)
+    if (cursors[i].name == NULL || strcmp (cursors[i].name, name) == 0)
+      return hcursor_from_x_cursor (i, cursors[i].type);
+
+  return NULL;
+}
+
+static HCURSOR
+hcursor_from_theme (GdkDisplay  *display,
+                    const gchar *name)
+{
+  Win32CursorTheme *theme;
+  Win32Cursor *theme_cursor;
+  GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display);
+
+  if (name == NULL)
+    return NULL;
+
+  theme = _gdk_win32_display_get_cursor_theme (win32_display);
+  theme_cursor = win32_cursor_theme_get_cursor (theme, name);
+
+  if (theme_cursor == NULL)
+    return NULL;
+
+  return win32_cursor_create_hcursor (theme_cursor);
+}
+
+static HCURSOR
+hcursor_from_name (GdkDisplay  *display,
+                   const gchar *name)
+{
+  HCURSOR hcursor;
+
+  if (strcmp (name, "none") == 0)
+    return hcursor_from_type (GDK_BLANK_CURSOR);
+
+  /* Try current theme first */
+  hcursor = hcursor_from_theme (display, name);
+
+  if (hcursor != NULL)
+    return hcursor;
+
+  hcursor = hcursor_idc_from_name (name);
+
+  if (hcursor != NULL)
+    return hcursor;
+
+  hcursor = hcursor_x_from_name (name);
+
+  return hcursor;
+}
+
 static GdkCursor*
-cursor_new_from_hcursor (HCURSOR       hcursor,
-                        GdkCursorType cursor_type)
+cursor_new_from_hcursor (HCURSOR        hcursor,
+                        const gchar   *name,
+                        GdkCursorType  cursor_type)
 {
   GdkWin32Cursor *private;
   GdkCursor *cursor;
@@ -136,21 +531,120 @@ cursor_new_from_hcursor (HCURSOR       hcursor,
                           "cursor-type", cursor_type,
                           "display", _gdk_display,
                          NULL);
+
+  private->name = g_strdup (name);
+
   private->hcursor = hcursor;
   cursor = (GdkCursor*) private;
 
   return cursor;
 }
 
+static gboolean
+_gdk_win32_cursor_update (GdkWin32Display *win32_display,
+                          GdkWin32Cursor  *cursor)
+{
+  HCURSOR hcursor = NULL;
+  Win32CursorTheme *theme;
+  Win32Cursor *theme_cursor;
+
+  /* Do nothing if this is not a named cursor. */
+  if (cursor->name == NULL)
+    return FALSE;
+
+  theme = _gdk_win32_display_get_cursor_theme (win32_display);
+  theme_cursor = win32_cursor_theme_get_cursor (theme, cursor->name);
+
+  if (theme_cursor != NULL)
+    hcursor = win32_cursor_create_hcursor (theme_cursor);
+
+  if (hcursor == NULL)
+    {
+      g_warning (G_STRLOC ": Unable to load %s from the cursor theme", cursor->name);
+
+      hcursor = hcursor_idc_from_name (cursor->name);
+
+      if (hcursor == NULL)
+        hcursor = hcursor_x_from_name (cursor->name);
+
+      if (hcursor == NULL)
+        return FALSE;
+    }
+
+  if (GetCursor () == cursor->hcursor)
+    SetCursor (hcursor);
+
+  if (!DestroyCursor (cursor->hcursor))
+    g_warning (G_STRLOC ": DestroyCursor (%p) failed: %lu", cursor->hcursor, GetLastError ());
+
+  cursor->hcursor = hcursor;
+
+  return TRUE;
+}
+
+void
+_gdk_win32_display_update_cursors (GdkWin32Display *display)
+{
+  GHashTableIter iter;
+  const char *name;
+  GdkWin32Cursor *cursor;
+
+  g_hash_table_iter_init (&iter, display->cursor_cache);
+
+  while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &cursor))
+    _gdk_win32_cursor_update (display, cursor);
+}
+
+void
+_gdk_win32_display_init_cursors (GdkWin32Display *display)
+{
+  display->cursor_cache = g_hash_table_new_full (g_str_hash,
+                                                 g_str_equal,
+                                                 NULL,
+                                                 g_object_unref);
+  display->cursor_theme_name = g_strdup ("system");
+}
+
+void
+_gdk_win32_display_finalize_cursors (GdkWin32Display *display)
+{
+  g_free (display->cursor_theme_name);
+
+  if (display->cursor_theme)
+    win32_cursor_theme_destroy (display->cursor_theme);
+
+  g_hash_table_destroy (display->cursor_cache);
+}
+
+
 GdkCursor*
 _gdk_win32_display_get_cursor_for_type (GdkDisplay   *display,
                                        GdkCursorType cursor_type)
 {
+  GEnumClass *enum_class;
+  GEnumValue *enum_value;
+  gchar *cursor_name;
   HCURSOR hcursor;
+  GdkCursor *result;
+  GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display);
 
-  g_return_val_if_fail (display == _gdk_display, NULL);
+  enum_class = g_type_class_ref (GDK_TYPE_CURSOR_TYPE);
+  enum_value = g_enum_get_value (enum_class, cursor_type);
+  cursor_name = g_strdup (enum_value->value_nick);
+  g_strdelimit (cursor_name, "-", '_');
+  g_type_class_unref (enum_class);
+
+  result = g_hash_table_lookup (win32_display->cursor_cache, cursor_name);
+  if (result)
+    {
+      g_free (cursor_name);
+      return g_object_ref (result);
+    }
+
+  hcursor = hcursor_from_name (display, cursor_name);
 
-  hcursor = hcursor_from_type (cursor_type);
+  if (hcursor == NULL)
+    hcursor = hcursor_from_type (cursor_type);
 
   if (hcursor == NULL)
     g_warning ("gdk_cursor_new_for_display: no cursor %d found", cursor_type);
@@ -158,67 +652,62 @@ _gdk_win32_display_get_cursor_for_type (GdkDisplay   *display,
     GDK_NOTE (CURSOR, g_print ("gdk_cursor_new_for_display: %d: %p\n",
                               cursor_type, hcursor));
 
-  return cursor_new_from_hcursor (hcursor, cursor_type);
-}
+  result = cursor_new_from_hcursor (hcursor, cursor_name, cursor_type);
 
-/* FIXME: The named cursors below are presumably not really useful, as
- * the names are Win32-specific. No GTK+ application developed on Unix
- * (and most cross-platform GTK+ apps are developed on Unix) is going
- * to look for cursors under these Win32 names anyway.
- *
- * Would the following make any sense: The ms-windows theme engine
- * calls some (to-be-defined private) API here in gdk/win32 to
- * register the relevant cursors used by the currently active XP
- * visual style under the names that libgtk uses to look for them
- * ("color-picker", "dnd-ask", "dnd-copy", etc), and then when libgtk
- * asks for those we return the ones registered by the ms-windows
- * theme engine, if any.
- */
+  if (result == NULL)
+    return result;
 
-static struct {
-  char *name;
-  char *id;
-} default_cursors[] = {
-  { "appstarting", IDC_APPSTARTING },
-  { "arrow", IDC_ARROW },
-  { "cross", IDC_CROSS },
-#ifdef IDC_HAND
-  { "hand",  IDC_HAND },
-#endif
-  { "help",  IDC_HELP },
-  { "ibeam", IDC_IBEAM },
-  { "left_ptr_watch", IDC_APPSTARTING },
-  { "sizeall", IDC_SIZEALL },
-  { "sizenesw", IDC_SIZENESW },
-  { "sizens", IDC_SIZENS },
-  { "sizenwse", IDC_SIZENWSE },
-  { "sizewe", IDC_SIZEWE },
-  { "uparrow", IDC_UPARROW },
-  { "wait", IDC_WAIT }
-};
+  /* Blank cursor case */
+  if (cursor_type == GDK_BLANK_CURSOR ||
+      !cursor_name ||
+      g_str_equal (cursor_name, "none") ||
+      g_str_equal (cursor_name, "blank_cursor"))
+    {
+      g_free (cursor_name);
+      return result;
+    }
+
+  g_hash_table_insert (win32_display->cursor_cache,
+                       cursor_name,
+                       g_object_ref (result));
+
+  return result;
+}
 
 GdkCursor*
 _gdk_win32_display_get_cursor_for_name (GdkDisplay  *display,
                                        const gchar *name)
 {
   HCURSOR hcursor = NULL;
-  int i;
+  GdkCursor *result;
+  GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display);
 
-  g_return_val_if_fail (display == _gdk_display, NULL);
+  result = g_hash_table_lookup (win32_display->cursor_cache, name);
+  if (result)
+    return g_object_ref (result);
+
+  hcursor = hcursor_from_name (display, name);
 
-  for (i = 0; i < G_N_ELEMENTS(default_cursors); i++)
-    {
-      if (0 == strcmp(default_cursors[i].name, name))
-        hcursor = LoadCursor (NULL, default_cursors[i].id);
-    }
   /* allow to load named cursor resources linked into the executable */
   if (!hcursor)
     hcursor = LoadCursor (_gdk_app_hmodule, name);
 
-  if (hcursor)
-    return cursor_new_from_hcursor (hcursor, GDK_X_CURSOR);
+  if (hcursor == NULL)
+    return NULL;
 
-  return NULL;
+  result = cursor_new_from_hcursor (hcursor, name, GDK_X_CURSOR);
+
+  /* Blank cursor case */
+  if (!name ||
+      g_str_equal (name, "none") ||
+      g_str_equal (name, "blank_cursor"))
+    return result;
+
+  g_hash_table_insert (win32_display->cursor_cache,
+                       g_strdup (name),
+                       g_object_ref (result));
+
+  return result;
 }
 
 GdkPixbuf *
@@ -419,10 +908,10 @@ _gdk_win32_cursor_get_surface (GdkCursor *cursor,
 }
 
 GdkCursor *
-_gdk_win32_display_get_cursor_for_surface (GdkDisplay *display,
-                                         cairo_surface_t  *surface,
-                                         gdouble          x,
-                                         gdouble          y)
+_gdk_win32_display_get_cursor_for_surface (GdkDisplay      *display,
+                                          cairo_surface_t *surface,
+                                          gdouble          x,
+                                          gdouble          y)
 {
   HCURSOR hcursor;
   GdkPixbuf *pixbuf;
@@ -448,7 +937,7 @@ _gdk_win32_display_get_cursor_for_surface (GdkDisplay *display,
   g_object_unref (pixbuf);
   if (!hcursor)
     return NULL;
-  return cursor_new_from_hcursor (hcursor, GDK_CURSOR_IS_PIXMAP);
+  return cursor_new_from_hcursor (hcursor, NULL, GDK_CURSOR_IS_PIXMAP);
 }
 
 gboolean
@@ -474,6 +963,12 @@ _gdk_win32_display_get_default_cursor_size (GdkDisplay *display,
 {
   g_return_if_fail (display == _gdk_display);
 
+  /* TODO: Use per-monitor DPI functions (8.1 and newer) or
+   * calculate DPI ourselves and use that, assuming that 72 dpi
+   * corresponds to 32x32 cursors. Take into account that DPI
+   * can be artificially increased by the user to make stuff bigger.
+   */
+
   if (width)
     *width = GetSystemMetrics (SM_CXCURSOR);
   if (height)
index 8a7ca4c48f63a59c79684a23120eb7ee59a81fb6..906f0bd63fef1b2f29ce8c517606253ccc5e67f6 100644 (file)
 #include "gdkwin32window.h"
 #include "gdkwin32.h"
 
+/**
+ * gdk_win32_display_set_cursor_theme:
+ * @display: (type GdkWin32Display): a #GdkDisplay
+ * @theme: (allow-none) the name of the cursor theme to use, or %NULL to unset
+ *         a previously set value
+ * @size: the cursor size to use, or 0 to keep the previous size
+ *
+ * Sets the cursor theme from which the images for cursor
+ * should be taken.
+ *
+ * If the windowing system supports it, existing cursors created
+ * with gdk_cursor_new(), gdk_cursor_new_for_display() and
+ * gdk_cursor_new_from_name() are updated to reflect the theme
+ * change. Custom cursors constructed with
+ * gdk_cursor_new_from_pixbuf() will have to be handled
+ * by the application (GTK+ applications can learn about
+ * cursor theme changes by listening for change notification
+ * for the corresponding #GtkSetting).
+ *
+ * Since: 3.18
+ */
+void
+gdk_win32_display_set_cursor_theme (GdkDisplay  *display,
+                                    const gchar *name,
+                                    const gint   size)
+{
+  gint cursor_size;
+  gint w, h;
+  Win32CursorTheme *theme;
+  GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display);
+
+  g_assert (win32_display);
+
+  if (name == NULL)
+    name = "system";
+
+  w = GetSystemMetrics (SM_CXCURSOR);
+  h = GetSystemMetrics (SM_CYCURSOR);
+
+  /* We can load cursors of any size, but SetCursor() will scale them back
+   * to this value. It's possible to break that restrictions with SetSystemCursor(),
+   * but that will override cursors for the whole desktop session.
+   */
+  cursor_size = (w == h) ? w : size;
+
+  if (win32_display->cursor_theme_name != NULL &&
+      g_strcmp0 (name, win32_display->cursor_theme_name) == 0 &&
+      win32_display->cursor_theme_size == cursor_size)
+    return;
+
+  theme = win32_cursor_theme_load (name, cursor_size);
+  if (theme == NULL)
+    {
+      g_warning ("Failed to load cursor theme %s", name);
+      return;
+    }
+
+  if (win32_display->cursor_theme)
+    {
+      win32_cursor_theme_destroy (win32_display->cursor_theme);
+      win32_display->cursor_theme = NULL;
+    }
+
+  win32_display->cursor_theme = theme;
+  g_free (win32_display->cursor_theme_name);
+  win32_display->cursor_theme_name = g_strdup (name);
+  win32_display->cursor_theme_size = cursor_size;
+
+  _gdk_win32_display_update_cursors (win32_display);
+}
+
+Win32CursorTheme *
+_gdk_win32_display_get_cursor_theme (GdkWin32Display *win32_display)
+{
+  Win32CursorTheme *theme;
+
+  g_assert (win32_display->cursor_theme_name);
+
+  theme = win32_display->cursor_theme;
+  if (!theme)
+    {
+      theme = win32_cursor_theme_load (win32_display->cursor_theme_name,
+                                       win32_display->cursor_theme_size);
+      if (theme == NULL)
+        {
+          g_warning ("Failed to load cursor theme %s",
+                     win32_display->cursor_theme_name);
+          return NULL;
+        }
+      win32_display->cursor_theme = theme;
+    }
+
+  return theme;
+}
 
 static gulong
 gdk_win32_display_get_next_serial (GdkDisplay *display)
@@ -542,11 +636,17 @@ gdk_win32_display_dispose (GObject *object)
 static void
 gdk_win32_display_finalize (GObject *object)
 {
+  GdkWin32Display *display_win32 = GDK_WIN32_DISPLAY (object);
+
+  _gdk_win32_display_finalize_cursors (display_win32);
+
+  G_OBJECT_CLASS (gdk_win32_display_parent_class)->finalize (object);
 }
 
 static void
-gdk_win32_display_init(GdkWin32Display *display)
+gdk_win32_display_init (GdkWin32Display *display)
 {
+  _gdk_win32_display_init_cursors (display);
 }
 
 static void
index 1623c7eb72a95e780a24571a5a0ff3bf0e374d39..585d958890b0bf14df9ae6a75157607d89f3f4cf 100644 (file)
@@ -26,6 +26,11 @@ struct _GdkWin32Display
 {
   GdkDisplay display;
 
+  Win32CursorTheme *cursor_theme;
+  gchar *cursor_theme_name;
+  int cursor_theme_size;
+  GHashTable *cursor_cache;
+
   /* WGL/OpenGL Items */
   guint have_wgl : 1;
   guint gl_version;
index 98b64a3edbf4e68973a8d259e3057598a0c0ad87..a94a1885e2cacc89869fa84b5aee73194e1a18f7 100644 (file)
@@ -37,6 +37,7 @@
 #include <gdk/gdkprivate.h>
 #include <gdk/gdkcursorprivate.h>
 #include <gdk/win32/gdkwindow-win32.h>
+#include <gdk/win32/gdkwin32display.h>
 
 #include "gdkinternals.h"
 
@@ -114,6 +115,8 @@ typedef struct _GdkWin32SingleFont      GdkWin32SingleFont;
 struct _GdkWin32Cursor
 {
   GdkCursor cursor;
+
+  gchar *name;
   HCURSOR hcursor;
 };
 
@@ -374,6 +377,42 @@ HICON _gdk_win32_pixbuf_to_hcursor (GdkPixbuf *pixbuf,
                                    gint       x_hotspot,
                                    gint       y_hotspot);
 
+void _gdk_win32_display_init_cursors (GdkWin32Display     *display);
+void _gdk_win32_display_finalize_cursors (GdkWin32Display *display);
+void _gdk_win32_display_update_cursors (GdkWin32Display   *display);
+
+typedef struct _Win32CursorTheme Win32CursorTheme;
+
+struct _Win32CursorTheme {
+  GHashTable *named_cursors;
+};
+
+typedef enum GdkWin32CursorLoadType {
+  GDK_WIN32_CURSOR_LOAD_FROM_FILE = 0,
+  GDK_WIN32_CURSOR_LOAD_FROM_RESOURCE_NULL = 1,
+  GDK_WIN32_CURSOR_LOAD_FROM_RESOURCE_THIS = 2,
+  GDK_WIN32_CURSOR_CREATE = 3,
+} GdkWin32CursorLoadType;
+
+typedef struct _Win32Cursor Win32Cursor;
+
+struct _Win32Cursor {
+  GdkWin32CursorLoadType load_type;
+  gunichar2 *resource_name;
+  gint width;
+  gint height;
+  guint load_flags;
+  gint xcursor_number;
+  GdkCursorType cursor_type;
+};
+
+Win32CursorTheme *win32_cursor_theme_load             (const gchar      *name,
+                                                       gint              size);
+Win32Cursor *     win32_cursor_theme_get_cursor       (Win32CursorTheme *theme,
+                                                       const gchar      *name);
+void              win32_cursor_theme_destroy          (Win32CursorTheme *theme);
+Win32CursorTheme *_gdk_win32_display_get_cursor_theme (GdkWin32Display  *win32_display);
+
 /* GdkDisplay member functions */
 GdkCursor *_gdk_win32_display_get_cursor_for_type (GdkDisplay   *display,
                                                   GdkCursorType cursor_type);
index 916c6fa0ff5aa2685e8981e3209942397a29e4c0..84d51fde37497c096dc89ec13486223aea7ea9ab 100644 (file)
@@ -50,6 +50,11 @@ typedef struct _GdkWin32DisplayClass GdkWin32DisplayClass;
 GDK_AVAILABLE_IN_ALL
 GType      gdk_win32_display_get_type            (void);
 
+GDK_AVAILABLE_IN_3_18
+void       gdk_win32_display_set_cursor_theme    (GdkDisplay  *display,
+                                                  const gchar *theme,
+                                                  gint         size);
+
 G_END_DECLS
 
 #endif /* __GDK_WIN32_DISPLAY_H__ */
index 765ae0107187f7162b265ed1c7be20635f1751a6..88a9478bdef36d2815902e2ac2effe090941b9fb 100644 (file)
@@ -1972,7 +1972,10 @@ gdk_win32_window_set_device_cursor (GdkWindow *window,
   if (cursor)
     impl->cursor = g_object_ref (cursor);
   else
-    impl->cursor = NULL;
+    /* Use default cursor otherwise. Setting it to NULL will make it use
+     * system-default cursor, which is not controlled by GTK cursor theming.
+     */
+    impl->cursor = _gdk_win32_display_get_cursor_for_type (_gdk_display, GDK_LEFT_PTR);
 
   /* Destroy the previous cursor */
   if (previous_cursor != NULL)
index 2356715cec438cbd64dc1b1c99631ed2822b017f..e754e8712ff3b61f640c0b7bacf8ed7d1261e41e 100644 (file)
 #include "quartz/gdkquartz.h"
 #endif
 
+#ifdef GDK_WINDOWING_WIN32
+#include "win32/gdkwin32.h"
+#endif
+
 #ifdef G_OS_WIN32
 #include "gtkwin32themeprivate.h"
 #endif
@@ -2918,7 +2922,7 @@ settings_update_cursor_theme (GtkSettings *settings)
 {
   gchar *theme = NULL;
   gint size = 0;
-#if defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND)
+#if defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND) || defined(GDK_WINDOWING_WIN32)
   GdkDisplay *display = gdk_screen_get_display (settings->priv->screen);
 #endif
 
@@ -2937,6 +2941,11 @@ settings_update_cursor_theme (GtkSettings *settings)
   if (GDK_IS_WAYLAND_DISPLAY (display))
     gdk_wayland_display_set_cursor_theme (display, theme, size);
   else
+#endif
+#ifdef GDK_WINDOWING_WIN32
+  if (GDK_IS_WIN32_DISPLAY (display))
+    gdk_win32_display_set_cursor_theme (display, theme, size);
+  else
 #endif
     g_warning ("GtkSettings Cursor Theme: Unsupported GDK backend\n");
   g_free (theme);